/*
 *  Scanner.cpp
 *  osnutshell
 *
 *  Created by Zachary  Heylmun on 4/6/10.
 *  Copyright 2010 University of Florida. All rights reserved.
 *
 */

#include "Scanner.h"

using std::string;
using std::vector;
using std::cout;
using std::cin;

namespace nutshell
{
	// Outputs prompt and accepts user response.
	// Repeats until the user's response matches one of the entries in the
	// valid string array. The comparison is case-insensitive. The capitalized
	// version of the response is returned.
	// eg. if valid[] = {"yes"}, and the user responds "yEs", the function will return "YES".
	string Scanner::prompt(string valid[], size_t length, string prompt)
	{
		if(length == 0)
			throw std::logic_error("Valid entry array for prompt must have at least one entry.");
	
		string validUpper[length];
		
		for (size_t i = 0; i < length; ++i)
		{
			validUpper[i] = valid[i];
			
			for (string::iterator itr = validUpper[i].begin(); itr != validUpper[i].end(); ++itr)
				*itr = std::toupper(*itr);
		}
	
		string in;
	
		do
		{
			cout << prompt << " ";
			cin >> in;
			
			for (string::iterator itr = in.begin(); itr != in.end(); ++itr)
				*itr = std::toupper(*itr);
		}
		while( std::find( validUpper, validUpper + length, in ) == validUpper + length );
		
		return in;
	}
	
	// Prompts the user for "(Y)es/(N)o" entry.
	// Accepts responses of {y, n} and is case insensitive.
	// Returns 0 for a response of N0
	// Returns 1 for a response of YES
	int Scanner::promptYesNo()
	{
		// output prompt
		cout << "(Y)es/(N)o? ";
		fflush(stdout);
		
		struct termios oldt;
		struct termios newt;
		
		// save current terminal settings
	    	tcgetattr(STDIN_FILENO, &oldt);  
	    	
	    	// set terminal settings to non-canonical
	    	newt = oldt; 
	    	newt.c_lflag &= ~(ICANON);
	    	newt.c_cc[VMIN] = 1;
  	    	newt.c_cc[VTIME] = 0;

	    	tcsetattr(STDIN_FILENO, TCSANOW, &newt); 
	    
	    	// read a character
	    	char c;
	   	read (STDIN_FILENO, &c, 1);
		
		// restore terminal settings
	    	tcsetattr(STDIN_FILENO, TCSANOW, &oldt); 
	    	
	    	cout << std::endl;
	    	
		if (c == 'y' || c == 'Y')
			return 1;
		else if (c == 'n' || c == 'N')
			return 0;
		else
			return promptYesNo();
	}
	
	// Prompts the user for "(Y)es/(N)o/(A)ll" entry.
	// Accepts responses of {y, n, a} and is case insensitive.
	// Returns 0 for a response of N0
	// Returns 1 for a response of YES
	// Return -1 for a response of ALL
	int Scanner::promptYesNoAll()
	{
		// output prompt
		cout << "(Y)es/(N)o/(A)ll? ";
		fflush(stdout);
		
		struct termios oldt;
		struct termios newt;
		
		// save current terminal settings
	    	tcgetattr(STDIN_FILENO, &oldt);  
	    	
	    	// change terminal settings to non-canonical
	    	newt = oldt; 
	    	newt.c_lflag &= ~(ICANON);
	    	newt.c_cc[VMIN] = 1;
  	    	newt.c_cc[VTIME] = 0;

	    	tcsetattr(STDIN_FILENO, TCSANOW, &newt); 
	    
	    	// read a character
	    	char c;
	   	read (STDIN_FILENO, &c, 1);
		
		// restore terminal settings
	    	tcsetattr(STDIN_FILENO, TCSANOW, &oldt); 
	    	
	    	cout << std::endl;
	    	
		if (c == 'a' || c == 'A')
			return -1;
		else if (c == 'y' || c == 'Y')
			return 1;
		else if (c == 'n' || c == 'N')
			return 0;
		else
			return promptYesNoAll();
	}

	// Scans input into a vector of strings in the form
	// {command1, arg1, arg2, |, command2, |, command3, <, file_in, >, file_out}
	vector<string> Scanner::scanCommands()
	{
		vector<string> tokens;
		
		string cmdStr;

		struct termios oldt;
		struct termios newt;
		
		// save current terminal settings
	    tcgetattr(STDIN_FILENO, &oldt);  
	    
	    // change terminal settings to non-canonical
	    newt = oldt; 
	    newt.c_lflag &= ~(ICANON | ECHO);
	    newt.c_cc[VMIN] = 1;
  	    newt.c_cc[VTIME] = 0;

	    tcsetattr(STDIN_FILENO, TCSANOW, &newt); 
		
		// get recent command lines for up/down arrow history
		const vector<string>& history = Command_Completion::get_recent_lines();
		int history_index = 0;
		
		// read command line
		char c;
		int i = 0;
		while(true)
		{
			// read a character
			read (STDIN_FILENO, &c, 1);
			
			if (c == '\004') 	// EOF read
			{
				// return bye command
				tokens.clear();
				tokens.push_back("bye");
				
				// restore terminal settings
				tcsetattr(0, TCSANOW, &oldt); 
				
				return tokens;
			}
			else if (c == '\n')		// newline read (end of command)
			{
				putchar(c);
				fflush(stdout);
				break;
			}
			else if (c == '\177')	// backspace read
			{
				// don't backspace beyond prompt
				if ( i > 0 )
				{
					putchar('\b');
					putchar(' ');
					putchar('\b');
					fflush(stdout);
					--i;
					cmdStr[i] = ' ';
				}
			}
			else if (c == 27)	// escape character read
			{
				read (STDIN_FILENO, &c, 1);
				if (c == 91)	// arrow key was pressed
				{
					read (STDIN_FILENO, &c, 1);
					if (c == 65) // up arrow pressed
					{
						// is there further history to look through?
						if (history.size() > history_index + 1)
							++history_index;	//move further back in history
							
						// get the command line from history
						string line = history[history.size()-history_index-1];
						
						// erase the current command line
						for (int k = 0; k < i; ++k)
						{
							putchar('\b');
							putchar(' ');
							putchar('\b');
						}
						
						// output the command line from history
						for (int k = 0; k < line.size(); ++k)
						{
							putchar( line[k] );
						}
						cmdStr = line;
						i = line.size();
						fflush(stdout);
					}
					else if (c == 66) // down arrow pressed
					{
						// if there newer history to look through?
						if ( history_index > 0)
							--history_index;	//move forward in history
							
						// get the command line from history
						string line = history[history.size()-history_index-1];
						
						// erase the current command line
						for (int k = 0; k < i; ++k)
						{
							putchar('\b');
							putchar(' ');
							putchar('\b');
						}
						
						// output the command line from history
						for (int k = 0; k < line.size(); ++k)
						{
							putchar( line[k] );
						}
						cmdStr = line;
						i = line.size();
						fflush(stdout);
					}
					else if (c == 67) // right arrow pressed
					{
						//move cursor right
						putchar(27);
						putchar(91);
						putchar(67);
						fflush(stdout);
						++i;
					}
					else if (c == 68) //left arrow pressed
					{
						// don't move beyond prompt
						if ( i > 0 )
						{
							//move cursor left
							putchar('\b');
							fflush(stdout);
							--i;
						}
					}
				}
				else if (c == 27) // double escape hit (command completion)
				{
					// parse the command prefix
					int e = i;
					int b = e;
					while (b > 0 && cmdStr[b-1] != ' ' && cmdStr[b-1] != '|')
						--b;
					string current_cmd(cmdStr.begin() + b, cmdStr.begin() + e);
					
					// get a vector of matches for the prefix
					vector<string> matches = Command_Completion::complete_command(current_cmd);
					if (matches.empty())
						continue;
						
					// erase the command prefix
					int erase_size = current_cmd.size();
					for (int k = 0; k < erase_size; k++)
					{
						putchar('\b');
					}
					cmdStr.erase(b, erase_size);
					
					// output the completed command
					int put_size = matches[0].size();
					for (int k = 0; k < put_size; k++)
					{
						putchar(matches[0][k]);
					}
					cmdStr.insert(b, matches[0]);
					
					fflush(stdout);
					i += put_size - erase_size;
				}
			}
			else if (c == '\t')
			{
				//ignore tabs
			}
			else		//normal character read
			{
				// output the character
				putchar(c);
				fflush(stdout);
				
				if (cmdStr.size() <= i)
					cmdStr.resize(i+1, ' ');
					
				cmdStr[i] = c;
				++i;
			}
		}
		
		// restore terminal settings
		tcsetattr(0, TCSANOW, &oldt); 
		
		// add command line for history lookup
		Command_Completion::add_command_line(cmdStr);
		
		// parse the command line into tokens
		string::const_iterator itr = cmdStr.begin();
		string::const_iterator jtr;
		string::const_iterator end = cmdStr.end();
		
		while (itr != end)
		{
			while (itr != end && std::isspace(*itr))
				++itr;
		
			jtr = itr;
			
			if ( *jtr == '|' || *jtr == '<' || *jtr == '>' )
					++jtr;
			else
				while (jtr != end && !std::isspace(*jtr) && *jtr != '|' && *jtr != '<' && *jtr != '>')
						++jtr;
				
			if (itr != jtr)
				tokens.push_back( string(itr, jtr) );
				
			itr = jtr;
		}
		
		return tokens;
	}
	
	vector<string> Scanner::scan_command(string cmdStr)
	{
		vector<string> tokens;
	
		string::const_iterator itr = cmdStr.begin();
		string::const_iterator jtr;
		string::const_iterator end = cmdStr.end();
		
		while (itr != end)
		{
			while (itr != end && std::isspace(*itr))
				++itr;
		
			jtr = itr;
			
			if ( *jtr == '|' || *jtr == '<' || *jtr == '>' )
					++jtr;
			else
				while (jtr != end && !std::isspace(*jtr) && *jtr != '|' && *jtr != '<' && *jtr != '>')
						++jtr;
				
			if (itr != jtr)
				tokens.push_back( string(itr, jtr) );
				
			itr = jtr;
		}
		
		return tokens;
	}
	
}
